package com.learning.netease.optimize.snowslide;

import com.google.common.base.Strings;
import com.learning.cache.redis.snowslide.DataDao;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * ClassName: CacheSnowSlideService
 * Description: 缓存雪崩解决方案
 * Date: 2019/1/21 10:43 【需求编号】
 *
 * @author Sam Sho
 * @version V1.0.0
 */
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class CacheSnowSlideService2 {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private DataDao dataDao;

    private final Lock lock = new ReentrantLock();

    /**
     * 优点：简单有效、适用范围广
     * 缺点：
     * - 锁阻塞其他线程，导致用户体验不好。1999 等待，1个人执行
     * - 锁的颗粒度太粗。所有缓存重建请求被一把锁阻塞
     *
     * @param name
     * @return
     */
    public String doService(String name) {

        // 1- 先从Redis 缓存获取数据
        String result = redisTemplate.opsForValue().get(name);

        if (!Strings.isNullOrEmpty(result)) {
            log.info(Thread.currentThread().getName() + " 缓存中取得数据 ---------> " + result);
            return result;
        }

        // 2000 个线程，只有1个拿到锁，1999个等待
        lock.lock();
        try {

            // 1-2 拿到锁以后再从缓存中取一遍，避免数据库的查询
            result = redisTemplate.opsForValue().get(name);
            if (!Strings.isNullOrEmpty(result)) {
                log.info(Thread.currentThread().getName() + " 缓存中取得数据 ---------> " + result);
                return result;
            }

            // 拿到锁的 重建缓存
            // 2- 缓存中没有，则取数据库
            result = dataDao.get(name);
            System.out.println(Thread.currentThread().getName() + " 从数据库中中取得数据 ============> " + result);


            // 3- 更新缓存，120秒超时
            redisTemplate.opsForValue().set(name, result, 120, TimeUnit.SECONDS);

        } finally {
            lock.unlock();
        }

        return result;
    }
}
